﻿/*******************************************************************************
Copyright (c) 2011-2013, PKStudio
Copyright (c) 2011,      Zynga Inc.
Copyright (c) 2008-2010, Ricardo Quesada
Copyright (c) 2009,      Valentin Milea

http://pkstudio.org

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*******************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;

namespace PKEngine
{
	/// <summary>
	/// Enum for RelativeType.
	/// </summary>
	public enum PKRelativeType
	{
		RelativeToParent,	// The position of node will be relatived to its parent.
		RelativeToScene,	// The position of node will be relatived to its root scene.
		Absolute,			// The position will be absolute, DO NOT use it unless you know what are you doing.
		Scene				// Only used for PKScene, DO NOT use it for other nodes.
	}

	/// <summary>
	/// PKNode is the main element. Anything that gets drawn or contains things that get drawn is a PKNode.
	/// </summary>
	public class PKNode : IPKUpdate
	{
		#region Public Constant
		public const int PKNodeInvalidTag = -1;
		#endregion

		#region Static Fields
		private static ulong globalOrderOfArrival = 1;
		#endregion

		#region Private Fields
		private Point position;
		private Point anchorPoint;
		private PointF anchorPointInPercent;
		private PKRelativeType isRelative;
		private Size contentSize;
		private bool isBoundingBoxDirty;
		private Rectangle boundingBox;
		private bool isReorderChildDirty;

		WeakReference pParent;
		#endregion

		#region Public Properties
		/// <summary>
		/// Position of PKNode.
		/// </summary>
		public Point Position
		{
			get
			{
				return this.position;
			}
			set
			{
				if (!this.position.Equals(value))
				{
					this.position = value;
					this.isBoundingBoxDirty = true;
				}
			}
		}

		/// <summary>
		/// AnchorPoint of PKNode.
		/// <remarks>This is the AnchorPointInPixel in cocos2D engine.</remarks>
		/// </summary>
		public Point AnchorPoint
		{
			get
			{
				return this.anchorPoint;
			}
			set
			{
				if (!this.anchorPoint.Equals(value))
				{
					this.anchorPoint = value;
					this.anchorPointInPercent = PointF.Empty;
					this.isBoundingBoxDirty = true;
				}
			}
		}

		/// <summary>
		/// AnchorPointInPercent of PKNode. It will not work if it is PointF.Empty.
		/// <remarks>This is the AnchorPoint in cocos2D engine.</remarks>
		/// <remarks>AnchorPointInPercent will be disabled auto when you set AnchorPoint manuallly.</remarks>
		/// </summary>
		public PointF AnchorPointInPercent
		{
			get
			{
				return anchorPointInPercent;
			}
			set
			{
				if (!this.anchorPointInPercent.Equals(value))
				{
					anchorPointInPercent = value;
					this.UpdateAnchorPointHelper();
					this.isBoundingBoxDirty = true;
				}
			}
		}
		/// <summary>
		/// If the node's location is relatived to its parent.
		/// </summary>
		public PKRelativeType IsRelative
		{
			get
			{
				return this.isRelative;
			}
			set
			{
				if (this.IsRelative != value)
				{
					this.isRelative = value;
					this.isBoundingBoxDirty = true;
				}
			}
		}

		/// <summary>
		/// ContentSize of PKNode.
		/// </summary>
		public Size ContentSize
		{
			get
			{
				return this.contentSize;
			}
			set
			{
				if (!this.contentSize.Equals(value))
				{
					this.contentSize = value;
					this.UpdateAnchorPointHelper();
					this.isBoundingBoxDirty = true;
				}
			}
		}

		/// <summary>
		/// BoundingBox of PKNode.
		/// It's a absolute value. It will be updated by Transform method.
		/// Transform will be called before drawing if the node is dirty.
		/// If you want to get the correct BoundingBox immediately you should call Transform method at first.
		/// </summary>
		public Rectangle BoundingBox
		{
			get
			{
				return this.boundingBox;
			}
		}

		/// <summary>
		/// Used to preserve sequence while sorting children with the same ZOrder.
		/// </summary>
		public ulong OrderOfArrival { get; internal set; }

		/// <summary>
		/// ZOrder of PKNode.
		/// </summary>
		public int ZOrder { get; internal set; }

		/// <summary>
		/// If the PKNode is visible.
		/// </summary>
		public bool IsVisible { get; set; }

		/// <summary>
		/// Children of PKNode.
		/// </summary>
		public List<PKNode> Children { get; private set; }

		/// <summary>
		/// Parent of PKNode.
		/// </summary>
		public PKNode Parent
		{
			get
			{
				if (pParent != null && pParent.IsAlive)
				{
					return pParent.Target as PKNode;
				}
				return null;
			}
			internal set
			{
				pParent = null;
				if (value != null)
				{
					pParent = new WeakReference(value);
				}
			}
		}

		/// <summary>
		/// Tag of PKNode.
		/// </summary>
		public int Tag { get; set; }

		/// <summary>
		/// If the PKNode is running.
		/// </summary>
		public bool IsRunning { get; private set; }

		/// <summary>
		/// A custom user data.
		/// </summary>
		public object UserData { get; set; }
		#endregion

		#region Initialization
		public PKNode()
		{
			this.Position = Point.Empty;
			this.AnchorPoint = Point.Empty;
			this.AnchorPointInPercent = PointF.Empty;
			this.IsRelative = PKRelativeType.RelativeToParent;
			this.ContentSize = Size.Empty;
			this.OrderOfArrival = 0;
			this.ZOrder = 0;
			this.IsVisible = true;
			this.Children = null;
			this.Parent = null;
			this.Tag = PKNodeInvalidTag;
			this.IsRunning = false;
			this.isBoundingBoxDirty = true;
			this.isReorderChildDirty = false;
			this.UserData = null;
		}
		#endregion

		#region Public Methods
		#region Children Management
		/// <summary>
		/// Gets a child in the container by given tag.
		/// </summary>
		/// <param name="tag">Target tag</param>
		/// <returns>Target child</returns>
		public PKNode GetChildByTag(int tag)
		{
			Debug.Assert(tag != PKNodeInvalidTag, "PKNode.GetChildByTag: Invalid tag.");

			if (Children != null && Children.Count != 0)
			{
				foreach (PKNode child in Children)
				{
					if (child.Tag == tag)
					{
						return child;
					}
				}
			}

			return null;
		}

		/// <summary>
		/// Adds a child into the container.
		/// </summary>
		/// <param name="child">Target child</param>
		/// <param name="zOrder">Target z order</param>
		/// <param name="tag">Target tag</param>
		public virtual void AddChild(PKNode child, int zOrder, int tag)
		{
			Debug.Assert(child != null, "PKNode.AddChild: Argument cannot be null.");
			Debug.Assert(child.Parent == null, @"PKNode.AddChild: Child has been added already. It can't be added again.");

			if (Children == null)
			{
				Children = new List<PKNode>();
			}

			this.InsertChildHelper(child, zOrder);

			child.Tag = tag;

			child.Parent = this;

			child.OrderOfArrival = globalOrderOfArrival++;

			if (IsRunning)
			{
				child.OnEnter();
				child.OnEnterTransitionDidFinish();
			}
		}

		/// <summary>
		/// Adds a child into the container.
		/// </summary>
		/// <param name="child">Target child</param>
		/// <param name="zOrder">Target z order</param>
		public virtual void AddChild(PKNode child, int zOrder)
		{
			this.AddChild(child, zOrder, child.Tag);
		}

		/// <summary>
		/// Adds a child into the container.
		/// </summary>
		/// <param name="child">Target child</param>
		public virtual void AddChild(PKNode child)
		{
			this.AddChild(child, child.ZOrder, child.Tag);
		}

		/// <summary>
		/// Removes itself from its parent.
		/// </summary>
		public void RemoveFromParent()
		{
			this.RemoveFromParent(true);
		}

		/// <summary>
		/// Removes itself from its parent.
		/// </summary>
		/// <param name="needCleanup">If we need to cleanup the child.</param>
		public void RemoveFromParent(bool needCleanup)
		{
			Parent.RemoveChild(this, needCleanup);
		}

		/// <summary>
		/// Removes a child from the container.
		/// </summary>
		/// <param name="child">Target child.</param>
		public virtual void RemoveChild(PKNode child)
		{
			this.RemoveChild(child, true);
		}

		/// <summary>
		/// Removes a child from the container.
		/// </summary>
		/// <param name="child">Target child.</param>
		/// <param name="needCleanup">If we need to cleanup the child.</param>
		public virtual void RemoveChild(PKNode child, bool needCleanup)
		{
			if (child == null)
			{
				return;
			}

			if (Children.Contains(child))
			{
				Debug.Assert(child.Parent == this, "PKNode.RemoveChild: The node should be the parent of its child.");
				this.RemoveChildHelper(child, needCleanup);
			}
		}

		/// <summary>
		/// Removes a child from the container by tag.
		/// </summary>
		/// <param name="tag">Target tag.</param>
		public void RemoveChildByTag(int tag)
		{
			this.RemoveChildByTag(tag, true);
		}

		/// <summary>
		/// Removes a child from the container by tag.
		/// </summary>
		/// <param name="tag">Target tag.</param>
		/// <param name="needCleanup">If we need to cleanup the child.</param>
		public void RemoveChildByTag(int tag, bool needCleanup)
		{
			Debug.Assert(tag != PKNodeInvalidTag, "PKNode.RemoveChildByTag: Invalid tag.");

			PKNode child = this.GetChildByTag(tag);
			if (child != null)
			{
				this.RemoveChild(child, needCleanup);
			}
		}

		/// <summary>
		/// Clears all children from the container.
		/// </summary>
		public void ClearChildren()
		{
			this.ClearChildren(true);
		}

		/// <summary>
		/// Clears all children from the container.
		/// </summary>
		/// <param name="needCleanup">If we need to cleanup the child.</param>
		public void ClearChildren(bool needCleanup)
		{
			if (Children != null && Children.Count > 0)
			{
				foreach (PKNode child in Children)
				{
					if (IsRunning)
					{
						child.OnExitTransitionDidStart();
						child.OnExit();
					}

					if (needCleanup)
					{
						child.Cleanup();
					}

					child.Parent = null;
				}

				Children.Clear();
			}
		}

		/// <summary>
		/// Reorders a child according to the new z order.
		/// </summary>
		/// <param name="child">Target child</param>
		/// <param name="zOrder">Tatget z order</param>
		public void ReorderChild(PKNode child, int zOrder)
		{
			if (child == null)
			{
				return;
			}

			if (Children.Contains(child))
			{
				Debug.Assert(child.Parent == this, "PKNode.ReorderChild: The node should be the parent of its child.");

				isReorderChildDirty = true;

				child.OrderOfArrival = globalOrderOfArrival++;
				child.ZOrder = zOrder;
			}
		}
		#endregion

		#region Main Features
		/// <summary>
		/// Push clip rect.
		/// </summary>
		protected virtual void PushClippingRect()
		{
			// Override this method if you need to clip children.
		}

		/// <summary>
		/// Pop clip rect.
		/// </summary>
		protected virtual void PopClippingRect()
		{
			// Override this method if you need to clip children.
		}

		/// <summary>
		/// Draws node.
		/// </summary>
		public virtual void Draw()
		{
			// Override this method. You should draw your nodes only in this method.
		}

		/// <summary>
		/// Visits all children and draw them.
		/// </summary>
		public virtual void Visit()
		{
			if (!IsVisible)
			{
				return;
			}

			this.Transform(false);

			this.PushClippingRect();
			if (Children != null && Children.Count > 0)
			{
				this.SortChildrenHelper();

				int index = 0;
				for (; index < Children.Count; index++)
				{
					if (Children[index].ZOrder < 0)
						Children[index].Visit();
					else
						break;
				}

				this.Draw();

				for (; index < Children.Count; index++)
				{
					Children[index].Visit();
				}
			}
			else
			{
				this.Draw();
			}
			this.PopClippingRect();
		}

		/// <summary>
		/// This method will be call when this PKNode is going to enter the "stage".
		/// </summary>
		public virtual void OnEnter()
		{
			if (Children != null && Children.Count != 0)
			{
				foreach (PKNode child in Children)
				{
					child.OnEnter();
				}
			}

			this.ResumeSchedulerAndActions();

			IsRunning = true;
		}

		/// <summary>
		/// This method will be call when this PKNode is going to exit the "stage".
		/// </summary>
		public virtual void OnExit()
		{
			this.PauseSchedulerAndActions();

			IsRunning = false;

			if (Children != null && Children.Count != 0)
			{
				foreach (PKNode child in Children)
				{
					child.OnExit();
				}
			}
		}

		/// <summary>
		/// This method will be call when the transition is finished.
		/// </summary>
		public virtual void OnEnterTransitionDidFinish()
		{
			if (Children != null && Children.Count != 0)
			{
				foreach (PKNode child in Children)
				{
					child.OnEnterTransitionDidFinish();
				}
			}
		}

		/// <summary>
		/// This method will be call when the transition is started.
		/// </summary>
		public virtual void OnExitTransitionDidStart()
		{
			if (Children != null && Children.Count != 0)
			{
				foreach (PKNode child in Children)
				{
					child.OnExitTransitionDidStart();
				}
			}
		}

		/// <summary>
		/// Cleanup for PKNode.
		/// </summary>
		public virtual void Cleanup()
		{
			this.StopAllActions();
			this.UnscheduleAllSchedules();

			if (Children != null && Children.Count != 0)
			{
				foreach (PKNode child in Children)
				{
					child.Cleanup();
				}
			}

		}

		/// <summary>
		/// Performs transformation based on position, anchor point and other properties.
		/// Do not call this method manually, unless you know what you are doing.
		/// If you need get the correct bounding box immediately, call PKGame.SharedGame.RunningScene.Transform().
		/// </summary>
		/// <param name="force">If we need to perform transformation by force</param>
		public void Transform(bool force)
		{
			if (!isBoundingBoxDirty && !force)
			{
				if (IsRelative == PKRelativeType.Scene)
				{
					PKMainSurface.SharedMainSurface.SceneRect = boundingBox;
				}
			}
			else
			{
				int x = Position.X - AnchorPoint.X;
				int y = Position.Y - AnchorPoint.Y;
				if (IsRelative == PKRelativeType.RelativeToParent && Parent != null)
				{
					x += Parent.BoundingBox.Left;
					y += Parent.BoundingBox.Top;
				}
				else if (IsRelative == PKRelativeType.RelativeToScene)
				{
					x += PKMainSurface.SharedMainSurface.SceneRect.Left;
					y += PKMainSurface.SharedMainSurface.SceneRect.Top;
				}
				boundingBox = new Rectangle(x, y, ContentSize.Width, ContentSize.Height);

				if (IsRelative == PKRelativeType.Scene)
				{
					PKMainSurface.SharedMainSurface.SceneRect = boundingBox;
				}

				isBoundingBoxDirty = false;

				if (Children != null && Children.Count != 0)
				{
					foreach (PKNode child in Children)
					{
						child.Transform(true);
					}
				}
			}
		}
		#endregion

		#region Action
		/// <summary>
		/// Executes an action, and returns the action that is executed.
		/// </summary>
		/// <param name="action">The target action</param>
		/// <returns>The target action<</returns>
		public PKAction RunAction(PKAction action)
		{
			Debug.Assert(action != null, "PKNode.RunAction: Argument cannot be null.");

			PKActionManager.SharedManager.AddAction(action, this, !IsRunning);
			return action;
		}

		/// <summary>
		/// Removes all actions from the running action list.
		/// </summary>
		public void StopAllActions()
		{
			PKActionManager.SharedManager.RemoveAllActionsFromTarget(this);
		}

		/// <summary>
		/// Removes an action from the running action list
		/// </summary>
		/// <param name="action">The target action</param>
		public void StopAction(PKAction action)
		{
			PKActionManager.SharedManager.RemoveAction(action);
		}

		/// <summary>
		/// Removes an action from the running action list given its tag.
		/// </summary>
		/// <param name="tag">Tag of action</param>
		public void StopActionByTag(int tag)
		{
			Debug.Assert(tag != PKAction.PKActionInvalidTag, "PKNode.StopActionByTag: Invalid tag.");
			PKActionManager.SharedManager.RemoveActionByTag(tag, this);
		}

		/// <summary>
		/// Gets an action from the running action list given its tag.
		/// </summary>
		/// <param name="tag">Tag of action</param>
		public PKAction GetActionByTag(int tag)
		{
			Debug.Assert(tag != PKAction.PKActionInvalidTag, "PKNode.GetActionByTag: Invalid tag.");
			return PKActionManager.SharedManager.GetActionByTag(tag, this);
		}
		/// <summary>
		/// Returns the numbers of actions that are running plus the ones that are schedule to run.
		/// Composable actions are counted as 1 action. Example:
		/// 	If you are running 1 Sequence of 7 actions, it will return 1.
		/// 	If you are running 7 Sequences of 2 actions, it will return 7.
		/// </summary>
		/// <returns>Numbers of running actions</returns>
		public int NumberOfRunningActions()
		{
			return PKActionManager.SharedManager.NumberOfRunningActionsInTarget(this);
		}
		#endregion

		#region Schedule
		/// <summary>
		/// Schedules the "Update" method with priority = 0.
		/// </summary>
		public void ScheduleUpdate()
		{
			this.ScheduleUpdateWithPriority(0);
		}

		/// <summary>
		/// Schedules the "Update" method with priority.
		/// The update method will be called every frame.
		/// Schedule with a lower priority will be called eailier.
		/// </summary>
		/// <param name="priority">The priority of schedule</param>
		public void ScheduleUpdateWithPriority(int priority)
		{
			PKScheduler.SharedScheduler.ScheduleUpdateForTarget(this, priority, !IsRunning);
		}

		/// <summary>
		/// Unschedules the "Update" method.
		/// </summary>
		public void UnscheduleUpdate()
		{
			PKScheduler.SharedScheduler.UnscheduleUpdateForTarget(this);
		}

		/// <summary>
		/// Schedules a delegate with interval time = 0.
		/// </summary>
		/// <param name="sDelegate">Target delegate</param>
		public void ScheduleDelegate(SchedulerDelegate sDelegate)
		{
			this.ScheduleDelegate(sDelegate, 0, -1, 0);
		}

		/// <summary>
		/// Schedules a delegate with interval time.
		/// If the interval time is 0, you should use "ScheduleUpdate" method instead of "Schedule" method.
		/// If the delegate has been scheduled, then the interval time of schedule will be updated.
		/// </summary>
		/// <param name="sDelegate">Target delegate</param>
		/// <param name="interval">Interval of schedule</param>
		public void ScheduleDelegate(SchedulerDelegate sDelegate, float interval)
		{
			this.ScheduleDelegate(sDelegate, interval, -1, 0);
		}

		/// <summary>
		/// Schedules a delegate with interval time.
		/// If the interval time is 0, you should use "ScheduleUpdate" method instead of "Schedule" method.
		/// If the delegate has been scheduled, then the interval time of schedule will be updated.
		/// </summary>
		/// <param name="sDelegate">Target delegate</param>
		/// <param name="interval">Interval of schedule</param>
		/// <param name="repeat">-1: repeat forever, 0: once, 1: twice, ...</param>
		/// <param name="delay">schedule will start after delay</param>
		public void ScheduleDelegate(SchedulerDelegate sDelegate, float interval, int repeat, float delay)
		{
			Debug.Assert(sDelegate != null, "PKNode.ScheduleDelegate: Argument cannot be null.");
			Debug.Assert(interval >= 0, "PKNode.ScheduleDelegate: Argument must be positive.");

			PKScheduler.SharedScheduler.ScheduleDelegateForTarget(this, sDelegate, interval, !IsRunning, repeat, delay);
		}

		/// <summary>
		/// Schedules a delegate once after a delay time.
		/// </summary>
		/// <param name="sDelegate">Target delegate</param>
		/// <param name="delay">schedule will start after delay</param>
		public void ScheduleOnce(SchedulerDelegate sDelegate, float delay)
		{
			this.ScheduleDelegate(sDelegate, 0, 0, delay);
		}

		/// <summary>
		/// Unschedules a delegate.
		/// </summary>
		/// <param name="sDelegate">Target delegate</param>
		public void UnscheduleDelegate(SchedulerDelegate sDelegate)
		{
			if (sDelegate == null)
			{
				return;
			}

			PKScheduler.SharedScheduler.UnscheduleDelegateForTarget(this, sDelegate);
		}

		/// <summary>
		/// Unschedules all schedules for this object.
		/// Actions are not affected by this method.
		/// </summary>
		public void UnscheduleAllSchedules()
		{
			PKScheduler.SharedScheduler.UnscheduleAllSchedulesForTarget(this);
		}

		/// <summary>
		/// Updates node.
		/// </summary>
		/// <param name="dt">Delta time</param>
		public virtual void Update(float dt)
		{
			// Override this method. You can update your nodes in this method.
		}
		#endregion

		#region Resume & Pause
		/// <summary>
		/// Resume all schedules and actions for this object.
		/// </summary>
		public void ResumeSchedulerAndActions()
		{
			PKScheduler.SharedScheduler.ResumeTarget(this);
			PKActionManager.SharedManager.ResumeTarget(this);
		}

		/// <summary>
		/// Pause all schedules and actions for this object.
		/// </summary>
		public void PauseSchedulerAndActions()
		{
			PKScheduler.SharedScheduler.PauseTarget(this);
			PKActionManager.SharedManager.PauseTarget(this);
		}
		#endregion
		#endregion

		#region Private Methods
		/// <summary>
		/// Helper method to insert a child into container.
		/// </summary>
		/// <param name="child">Target child</param>
		/// <param name="zOrder">Target z order</param>
		private void InsertChildHelper(PKNode child, int zOrder)
		{
			isReorderChildDirty = true;

			Children.Add(child);
			child.ZOrder = zOrder;
		}

		/// <summary>
		/// Helper method to remove a child from the container.
		/// </summary>
		/// <param name="child">Target child.</param>
		/// <param name="needCleanup">If we need to cleanup the child.</param>
		private void RemoveChildHelper(PKNode child, bool needCleanup)
		{
			if (IsRunning)
			{
				child.OnExitTransitionDidStart();
				child.OnExit();
			}

			if (needCleanup)
			{
				child.Cleanup();
			}

			child.Parent = null;

			Children.Remove(child);
		}

		/// <summary>
		/// Helper method to sort all children.
		/// </summary>
		private void SortChildrenHelper()
		{
			if (isReorderChildDirty)
			{
				Children.Sort((PKNode item1, PKNode item2) =>
				{
					if (item1.ZOrder == item2.ZOrder)
					{
						return item1.OrderOfArrival.CompareTo(item2.OrderOfArrival);
					}
					return item1.ZOrder.CompareTo(item2.ZOrder);
				});
				isReorderChildDirty = false;
			}
		}

		/// <summary>
		/// Helper method to update the anchor point based on content size and anthor point in percent.
		/// </summary>
		private void UpdateAnchorPointHelper()
		{
			if (!anchorPointInPercent.IsEmpty)
			{
				anchorPoint.X = (int)(anchorPointInPercent.X * contentSize.Width);
				anchorPoint.Y = (int)(anchorPointInPercent.Y * contentSize.Height);
			}
		}
		#endregion
	}

	public class PKNodeRgba : PKNode, IPKRgba
	{
		#region Private Fields
		private int realOpacity;
		private int realColorOpacity;
		private ushort realColor;
		#endregion

		#region Properties
		/// <summary>
		/// Opacity of PKNodeRGBA.
		/// Range: 0 ~ 256
		/// </summary>
		public int Opacity
		{
			get
			{
				return this.realOpacity;
			}
			set
			{
				if (value < 0)
				{
					value = 0;
				}
				else if (value > 256)
				{
					value = 256;
				}
				this.DisplayedOpacity = this.realOpacity = value;

				if (this.CascadeOpacityEnabled)
				{
					int parentOpacity = 256;
					if (this.Parent != null && this.Parent is IPKRgba && ((IPKRgba)this.Parent).CascadeOpacityEnabled)
					{
						parentOpacity = ((IPKRgba)this.Parent).DisplayedOpacity;
					}
					this.UpdateDisplayedOpacity(parentOpacity);
				}
			}
		}

		/// <summary>
		/// ColorOpacity of PKNodeRGBA.
		/// Range: 0 ~ 256
		/// </summary>
		public int ColorOpacity
		{
			get
			{
				return this.realColorOpacity;
			}
			set
			{
				if (value < 0)
				{
					value = 0;
				}
				else if (value > 256)
				{
					value = 256;
				}
				this.DisplayedColorOpacity = this.realColorOpacity = value;

				if (this.CascadeColorEnabled)
				{
					ushort parentColor = PKAlpha.SharedAlpha.ColorWhite;
					int parentColorOpacity = 256;
					if (this.Parent != null && this.Parent is IPKRgba && ((IPKRgba)this.Parent).CascadeColorEnabled)
					{
						parentColor = ((IPKRgba)this.Parent).DisplayedColor;
						parentColorOpacity = ((IPKRgba)this.Parent).DisplayedColorOpacity;
					}
					this.UpdateDisplayedColor(parentColor, parentColorOpacity);
				}
			}
		}

		/// <summary>
		/// Color of PKNodeRGBA.
		/// </summary>
		public ushort Color
		{
			get
			{
				return this.realColor;
			}
			set
			{
				this.DisplayedColor = this.realColor = value;

				if (this.CascadeColorEnabled)
				{
					ushort parentColor = PKAlpha.SharedAlpha.ColorWhite;
					int parentColorOpacity = 256;
					if (this.Parent != null && this.Parent is IPKRgba && ((IPKRgba)this.Parent).CascadeColorEnabled)
					{
						parentColor = ((IPKRgba)this.Parent).DisplayedColor;
						parentColorOpacity = ((IPKRgba)this.Parent).DisplayedColorOpacity;
					}
					this.UpdateDisplayedColor(parentColor, parentColorOpacity);
				}
			}
		}

		/// <summary>
		/// Displayed Opacity.
		/// </summary>
		public int DisplayedOpacity { get; private set; }

		/// <summary>
		/// Displayed ColorOpacity.
		/// </summary>
		public int DisplayedColorOpacity { get; private set; }

		/// <summary>
		/// Displayed Color.
		/// </summary>
		public ushort DisplayedColor { get; private set; }

		/// <summary>
		/// Whether or not opacity should be propagated to its children.
		/// </summary>
		public bool CascadeOpacityEnabled { get; set; }

		/// <summary>
		/// Whether or not color should be propagated to its children.
		/// </summary>
		public bool CascadeColorEnabled { get; set; }
		#endregion

		#region Initialization
		public PKNodeRgba()
			: base()
		{
			this.DisplayedColor = this.Color = PKAlpha.SharedAlpha.ColorWhite;
			this.DisplayedOpacity = this.Opacity = this.DisplayedColorOpacity = this.ColorOpacity = 256;
			this.CascadeOpacityEnabled = this.CascadeColorEnabled = false;
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Recursive method that updates display opacity.
		/// </summary>
		public void UpdateDisplayedOpacity(int parentOpacity)
		{
			this.DisplayedOpacity = realOpacity * parentOpacity / 256;

			if (this.CascadeOpacityEnabled)
			{
				foreach (PKNode child in this.Children)
				{
					if (child is IPKRgba)
					{
						((IPKRgba)child).UpdateDisplayedOpacity(this.DisplayedOpacity);
					}
				}
			}
		}

		/// <summary>
		/// Recursive method that updates display color.
		/// </summary>
		public void UpdateDisplayedColor(ushort parentColor, int parentColorOpacity)
		{
			this.DisplayedColorOpacity = parentColorOpacity + realColorOpacity * (256 - parentColorOpacity) / 256;
			this.DisplayedColor = PKAlpha.SharedAlpha.PixelAlpha(this.realColor, parentColor, realColorOpacity);

			if (this.CascadeColorEnabled)
			{
				foreach (PKNode child in this.Children)
				{
					if (child is IPKRgba)
					{
						((IPKRgba)child).UpdateDisplayedColor(this.DisplayedColor, this.DisplayedColorOpacity);
					}
				}
			}
		}
		#endregion
	}

	public class PKNodeClipping : PKNode
	{
		#region Init
		public PKNodeClipping()
			: base()
		{
		}
		#endregion

		#region Public Methods
		protected override void PushClippingRect()
		{
			PKMainSurface.SharedMainSurface.PushClippingRect(BoundingBox);
		}

		protected override void PopClippingRect()
		{
			PKMainSurface.SharedMainSurface.PopClippingRect();
		}
		#endregion
	}
}
